package sim.ring.gui;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.jface.action.CoolBarManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ColumnViewerEditor;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
import org.eclipse.jface.viewers.FocusCellOwnerDrawHighlighter;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerEditor;
import org.eclipse.jface.viewers.TreeViewerFocusCellManager;
import org.eclipse.jface.window.ApplicationWindow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.TreeItem;

import sim.ring.gui.actions.AddNewCategoryAction;
import sim.ring.gui.actions.AddNewFolderAction;
import sim.ring.gui.actions.AddNewMethodAction;
import sim.ring.gui.actions.ConfigAppAction;
import sim.ring.gui.actions.ExitAction;
import sim.ring.gui.actions.InfoDialogAction;
import sim.ring.gui.actions.RemoveNodeAction;
import sim.ring.gui.treedata.Category;
import sim.ring.gui.treedata.DataModel;
import sim.ring.gui.treedata.Folder;
import sim.ring.gui.treedata.MethodTreeContentProvider;
import sim.ring.gui.treedata.MethodTreeLabelProvider;
import sim.ring.gui.treedata.TMethod;
import sim.ring.method.MethodQueryBuilder;
import sim.ring.technical.data.DatabaseConnection;

public class RingingSimApp extends ApplicationWindow {
	
	private static Logger log = Logger.getLogger("sim.ring.gui");
	protected HashMap<String, String> localisedData = new HashMap<String, String>();
	protected HashMap<String, String> configData = new HashMap<String, String>();
	private DatabaseConnection db = new DatabaseConnection();
	
	protected TreeViewer tv;
	protected Folder rootFolder;
	
	/**
	 * @return the tv
	 */
	public TreeViewer getTV() {
		return tv;
	}

	private final static String DBNAME = "RingingSimDB";
	
	/**
	 * @wbp.parser.constructor
	 */
	public RingingSimApp(Shell parentShell) {
		super(parentShell);
	}
	
	@Override
	public boolean close() {
		if (MessageDialog.openConfirm(Display.getCurrent().getActiveShell(),
				ExitAction._CLOSECONFIRMDIALOGTITLE, ExitAction._CLOSECONFIRMDIALOGMESSAGE)) {
			storeMethodTreeData();
		}
		return super.close();
	}
	
	public String updateConfigData(String key, String value, boolean makePersistent) {
		log.debug(String.format("Key: %s Value: %s", key, value));
		if (configData.containsKey(key)) {
			configData.remove(key);
		}
		configData.put(key, value);
		if (makePersistent) {
			storeConfigData(key, value);
		}
		return value;
	}
	
	public RingingSimApp() {
		super(null);
		
		log.info("After super constructor ...");
		
		log.info("Retrieving configuration data");
		retrieveConfigData();
		log.info(String.format("%d configuration entries retrieved", configData.size()));
		
		log.info("Retrieving language specific data");
		retrieveLanguageData();
		log.info(String.format("%d entries retrieved", localisedData.size()));
		
		addMenuBar();
		log.info("Menu bar created");
		addCoolBar(SWT.NONE);
		log.info("Cool bar created");
		addStatusLine();
		log.info("Status line created");
		
	}
	
	@Override
	protected void configureShell(Shell shell) {
		super.configureShell(shell);
		shell.setText(getLocalisedData("applicationTitle", "RingingSim"));
	}
	
	private void retrieveConfigData() {
		ResultSet result;
		try {
			db.connectToDB(DBNAME);
			Vector<Object> params = new Vector<Object>();
			params.add("RingingSimApp");
			result = db.issueQueryStatement(
					"SELECT * FROM gui_config " +
					"WHERE gui_area = ?", params);
			while (result.next()) {
				configData.put(result.getString("config_key"), result.getString("config_value"));
				log.debug(String.format("Key: %s Value: %s", result.getString("config_key"), result.getString("config_value")));
			}
		} catch (SQLException e) {
			log.error(e.getMessage());
		} finally {
			db.closeDB();
		}
	}

	private void retrieveLanguageData() {
		String language = "EN";
		log.debug("Number of keys in configData: " + configData.keySet().size());
		if (configData.containsKey("GUILanguage")) {
			language = configData.get("GUILanguage").toUpperCase();
		}
		log.debug(String.format("GUI language set to %s", language));
		ResultSet result;
		try {
			db.connectToDB(DBNAME);
			Vector<Object> params = new Vector<Object>();
			params.add("RingingSimApp");
			params.add(language);
			result = db.issueQueryStatement(
					"SELECT * FROM gui_localisation " +
					"WHERE gui_area = ? AND " +
					"gui_language = ?",params);
			while (result.next()) {
				localisedData.put(result.getString("local_key"), result.getString("local_value"));
			}
		} catch (SQLException e) {
			log.error(e.getMessage());
		} finally {
			db.closeDB();
		}	
	}
	
	public String getLocalisedData(String key, String alternativeValue) {
		if (localisedData.containsKey(key)) {
			String value = localisedData.get(key);
			log.debug(String.format("Localised text (Key %s): %s", key, value));
			return value; 
		} else {
			return alternativeValue;
		}
	}
	
	public String getConfigData(String key) {
		String value = "";
		if (configData.containsKey(key)) {
			value = configData.get(key);
			log.debug(String.format("Stored config data (Key %s): %s", key, value));
		} else {
			log.debug(String.format("Key %s not in config data", key));
		}
		return value;
	}
	
	public boolean storeConfigData(String key, String value) {
		boolean result = false;
		try {
			db.connectToDB(DBNAME);
			Vector<Object> params = new Vector<Object>();
			params.add(key);
			ResultSet countConfigKey = db.issueQueryStatement(
					"SELECT COUNT(*) FROM gui_config " +
					"WHERE gui_area = 'RingingSimApp' AND " +
					"config_key = ?", params);
			log.debug(countConfigKey.getFetchSize() + " entries retrieved.");
			int count = 0;
			if (countConfigKey.next()) {
				count = countConfigKey.getInt(1);
				log.debug("Count is : "  +count);
			}
			if (count == 0) {
				Vector<Object> insertParams = new Vector<Object>();
				insertParams.add("RingingSimApp");
				insertParams.add(key);
				insertParams.add(value);
				int sqlRC = db.issueNonQueryStatement("INSERT INTO gui_config ( " +
						"gui_area, " +
						"config_key, " +
						"config_value ) VALUES ( ?, ?, ? )",
						insertParams);
				log.debug("Return code (Insert): " + sqlRC);
			} else {
				Vector<Object> updateParams = new Vector<Object>();
				updateParams.add(value);
				updateParams.add("RingingSimApp");
				updateParams.add(key);
				int sqlRC = db.issueNonQueryStatement("UPDATE gui_config " +
						"SET config_value = ? " +
						"WHERE gui_area = ? AND " +
						"config_key = ? ", updateParams);
				log.debug("Return code (Update): " + sqlRC);
			}
		}
		catch (Exception exc) {
			log.error(exc.getMessage());
		}
		finally {
			db.closeDB();
		}
		return result;
	}
	
	protected CoolBarManager createCoolBarManager(int style) {
		CoolBarManager coolBar = new CoolBarManager(style);
		
		IToolBarManager bar1 = new ToolBarManager(style);
		
		bar1.add(new AddNewFolderAction(this));
		bar1.add(new AddNewCategoryAction(this));
		bar1.add(new AddNewMethodAction(this));
		bar1.add(new RemoveNodeAction(this));
		

		IToolBarManager bar2 = new ToolBarManager(style);
		bar2.add(new ExitAction(this));
		bar2.add(new ConfigAppAction(this));
		
		coolBar.add(bar1);
		coolBar.add(bar2);
		return coolBar;
	}
	
	@Override
	public void create() {
		super.create();
		//getShell().setText(getLocalisedData("applicationTitle", "RingingSim"));
	}
	
	private void createTreeViewer(Composite parent) {
		tv = new TreeViewer(parent, SWT.MULTI |
								    SWT.H_SCROLL |
								    SWT.V_SCROLL |
								    SWT.FULL_SELECTION);
		
		tv.setCellEditors(new CellEditor[] {
				new TextCellEditor(tv.getTree())
		});
		tv.setColumnProperties(new String[] { "Col1" });
		tv.setCellModifier(new ICellModifier() {
			
			@Override
			public void modify(Object element, String property, Object value) {
				element = ((TreeItem)element).getData();
				((DataModel)element).setName(value.toString());
				if (element instanceof TMethod) {
					((TMethod)element).updateMethodName();
				}
				tv.update(element, null);
			}
			
			@Override
			public Object getValue(Object element, String property) {
				return ((DataModel)element).getName();
			}
			
			@Override
			public boolean canModify(Object element, String property) {
				return true;
			}
		});
		
		TreeViewerFocusCellManager treeViewerFCM = new TreeViewerFocusCellManager(tv, 
				new FocusCellOwnerDrawHighlighter(tv));
		ColumnViewerEditorActivationStrategy actSupport = new ColumnViewerEditorActivationStrategy(tv) {
			@Override
			protected boolean isEditorActivationEvent(
					ColumnViewerEditorActivationEvent event) {
				return event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL
						|| event.eventType == ColumnViewerEditorActivationEvent.MOUSE_DOUBLE_CLICK_SELECTION
						|| (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == SWT.CR)
						|| (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == SWT.F2)
						|| event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC;
			}
		};
		TreeViewerEditor.create(tv, treeViewerFCM, actSupport, 
				ColumnViewerEditor.TABBING_HORIZONTAL
				| ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR
				| ColumnViewerEditor.TABBING_VERTICAL
				| ColumnViewerEditor.KEYBOARD_ACTIVATION);
		
		tv.setContentProvider(new MethodTreeContentProvider());
		tv.setLabelProvider(new MethodTreeLabelProvider());
		//tv.setInput(createTestInputData());
		tv.setInput(loadMethodTreeData());
	}
	
	@Override
	protected Control createContents(Composite parent) {	
		SashForm mainForm = new SashForm(parent, SWT.HORIZONTAL);
		
		createTreeViewer(mainForm);
		
		Text dummyText1 = new Text(mainForm, SWT.CENTER);
		dummyText1.setText("Test text");
		
		SashForm subSash = new SashForm(mainForm, SWT.VERTICAL);
		
		Text dummyText2 = new Text(subSash, SWT.CENTER);
		dummyText2.setText("Dummy text");
		
		Text dummyText3 = new Text(subSash, SWT.CENTER);
		dummyText3.setText("Another dummy text");
		
		subSash.setWeights(new int[]{ 2 , 1 });
		mainForm.setWeights(new int[]{ 1, 2, 1 });
		
		return mainForm;
	}
	
	protected MenuManager createMenuManager() {
		MenuManager menuBar = new MenuManager("");
		
		MenuManager fileMenu = new MenuManager(getLocalisedData("fileMenu", "&File"));
		MenuManager editMenu = new MenuManager(getLocalisedData("editMenu", "&Edit"));
		MenuManager optionMenu = new MenuManager(getLocalisedData("configMenu", "&Options"));
		
		menuBar.add(fileMenu);
		menuBar.add(editMenu);
		menuBar.add(optionMenu);
		
		fileMenu.add(new AddNewFolderAction(this));
		fileMenu.add(new AddNewCategoryAction(this));
		fileMenu.add(new AddNewMethodAction(this));
		fileMenu.add(new RemoveNodeAction(this));
		fileMenu.add(new Separator());
		fileMenu.add(new ExitAction(this));
		optionMenu.add(new ConfigAppAction(this));
		optionMenu.add(new InfoDialogAction(this));
		
		return menuBar;
	}
	
	public static void main(String[] args) {
		Display display = new Display();
		Realm.runWithDefault(SWTObservables.getRealm(display), new Runnable() {	
			@Override
			public void run() {
				RingingSimApp r = new RingingSimApp();
				r.setBlockOnOpen(true);
				setDefaultImage(RingingSimManager.getIMGReg().get("bellIcon"));
				r.open();
				Display.getCurrent().dispose();
			}
		});
	}
	
	private Folder createTestInputData() {
		rootFolder = new Folder();
		Folder f = new Folder("Test folder");
		Category c = new Category("Test category");
		TMethod m = new TMethod();
		c.add(m);
		f.add(c);
		rootFolder.add(f);
		return rootFolder;
	}
	
	private Folder loadMethodTreeData() {
		rootFolder = new Folder();
		HashMap<Integer, DataModel> loadedData = new HashMap<Integer, DataModel>();
		try {
			db.connectToDB(DBNAME);
			ResultSet rs = db.issueQueryStatement(DBStrings.M_T_SELECT_ALL_COMMAND, null);
			while (rs.next()) {
				Integer objId = new Integer(rs.getInt("tree_id"));
				String objName = rs.getString("model_name");
				String objType = rs.getString("model_type");
				int parentId = rs.getInt("parent_id");
				int methodId = rs.getInt("method_id");
				if (objType.equals("F")) {
					Folder f = new Folder(objName, objId.intValue());
					if (parentId != 0) {
						((DataModel)loadedData.get(new Integer(parentId))).add(f);
					} else {
						rootFolder.add(f);
					}
					loadedData.put(objId, f);
				} else if (objType.equals("C")) {
					Category c = new Category(objName, objId.intValue());
					if (parentId != 0) {
						((DataModel)loadedData.get(new Integer(parentId))).add(c);
					} else {
						rootFolder.add(c);
					}
					loadedData.put(objId, c);
				} else if (objType.equals("M")) {
					TMethod m = new TMethod(objName, objId.intValue());
					m.setMethod(MethodQueryBuilder.loadMethod(methodId));
					if (parentId != 0) {
						((DataModel)loadedData.get(new Integer(parentId))).add(m);
					} else {
						rootFolder.add(m);
					}
					loadedData.put(objId, m);
				}
				
			}
		}
		catch (SQLException e) {
			Iterator<Throwable> it = e.iterator();
			while (it.hasNext()) {
				log.error(it.next().getMessage());
			}
		}
		finally {
			db.closeDB();
		}
		
		return rootFolder;
	}

	public void storeMethodTreeData() {
		log.debug("Shutting down application");
		
		try {
			db.connectToDB(DBNAME);
			if (!db.checkForTable("METHOD_TREE")) {
				int sqlRC = db.issueNonQueryStatement(DBStrings.M_T_CREATE_COMMAND, null);
				log.debug("SQL RC: " + sqlRC);
			}
			saveDataModelToDB(rootFolder);
		}
		catch (Exception exc) {
			if (exc instanceof SQLException) {
				Iterator<Throwable> it = ((SQLException)exc).iterator();
				while (it.hasNext()) {
					log.error(it.next().getMessage());
				}
			} else {
				log.error(exc.getMessage());
			}
			MessageDialog.openError(getParentShell(),
					getLocalisedData("saveTreeErrorTitle", "Error while saving tree"),
					getLocalisedData("saveTreeErrorMessage", String.format("An error occurred while trying to save the method tree. Error details: %s",
							exc.getLocalizedMessage())));
		}
		finally {
			db.closeDB();
		}
	}
	
	private void saveDataModelToDB(DataModel model) throws SQLException {
		if (model instanceof Folder) {
			Folder thisF = (Folder)model;
			if (model.equals(rootFolder)) {
				log.debug("Root folder: do not save this model");
			} else {
				log.debug("Save folder: " + thisF.getId() + " / " + thisF.getName());
				if (thisF.getId()==0) {
					Vector<Object> params = new Vector<Object>();
					params.add(thisF.getName());
					params.add("F");
					params.add(thisF.getParent().getId());
					params.add(0);
					int sqlRC = db.issueNonQueryStatement(DBStrings.M_T_INSERT_COMMAND, params);
					thisF.setId(sqlRC);
					log.debug("SQL RC: " + sqlRC);
				} else {
					Vector<Object> params = new Vector<Object>();
					params.add(thisF.getName());
					params.add("F");
					params.add(thisF.getParent().getId());
					params.add(0);
					params.add(thisF.getId());
					int sqlRC = db.issueNonQueryStatement(DBStrings.M_T_UPDATE_COMMAND, params);
					log.debug("SQL RC: " + sqlRC);
				}
			}
			for (Folder f : thisF.getFolders()) {
				saveDataModelToDB(f);
			}
			for (Category c : thisF.getCategories()) {
				saveDataModelToDB(c);
			}
			for (TMethod t : thisF.getMethods()) {
				saveDataModelToDB(t);
			}
		} else if (model instanceof Category) {
			Category thisC = (Category)model;
			log.debug("Save category: " + thisC.getId() + " / " + thisC.getName());
			if (thisC.getId()==0) {
				Vector<Object> params = new Vector<Object>();
				params.add(thisC.getName());
				params.add("C");
				params.add(thisC.getParent().getId());
				params.add(0);
				int sqlRC = db.issueNonQueryStatement(DBStrings.M_T_INSERT_COMMAND, params);
				thisC.setId(sqlRC);
				log.debug("SQL RC: " + sqlRC);
			} else {
				Vector<Object> params = new Vector<Object>();
				params.add(thisC.getName());
				params.add("C");
				params.add(thisC.getParent().getId());
				params.add(0);
				params.add(thisC.getId());
				int sqlRC = db.issueNonQueryStatement(DBStrings.M_T_UPDATE_COMMAND, params);
				log.debug("SQL RC: " + sqlRC);
			}
			for (TMethod t : thisC.getMethods()) {
				saveDataModelToDB(t);
			}
		} else if (model instanceof TMethod) {
			TMethod thisM = (TMethod)model;
			log.debug("Save method: " + thisM.getId() + " / " + thisM.getName());
			MethodQueryBuilder.saveMethod(thisM.getMethod());
			if (thisM.getId()==0) {
				Vector<Object> params = new Vector<Object>();
				params.add(thisM.getName());
				params.add("M");
				params.add(thisM.getParent().getId());
				params.add(thisM.getMethod().getId());
				int sqlRC = db.issueNonQueryStatement(DBStrings.M_T_INSERT_COMMAND, params);
				thisM.setId(sqlRC);
				log.debug("SQL RC: " + sqlRC);
			} else {
				Vector<Object> params = new Vector<Object>();
				params.add(thisM.getName());
				params.add("M");
				params.add(thisM.getParent().getId());
				params.add(thisM.getMethod().getId());
				params.add(thisM.getId());
				int sqlRC = db.issueNonQueryStatement(DBStrings.M_T_UPDATE_COMMAND, params);
				log.debug("SQL RC: " + sqlRC);
			}
		}
	}

	public void deleteElementFromDB(int id) {
		try {
			db.connectToDB(DBNAME);
			Vector<Object> params = new Vector<Object>();
			params.add(id);
			params.add(id);
			db.issueNonQueryStatement(DBStrings.M_T_DELETE_COMMAND, params);
		}
		catch (SQLException e) {
			Iterator<Throwable> it = e.iterator();
			while (it.hasNext()) {
				log.error(it.next().getMessage());
			}
		}
		finally {
			db.closeDB();
		}
	}
}
